package rubik;

import aima.core.agent.Action;
import aima.core.agent.impl.DynamicAction;
import java.util.Arrays;

public class Cube {

    private Face u, d, l, r, f, b; //up,down,left,right,front,back
    private char[] vect;
    private int n;
    private String lastmove;
    
    public static final Cube SOLVED2x2 = new Cube(2,false);
    public static final Cube SOLVED3x3 = new Cube(3,false);
    private static CubeGraphics cg;
    
    public static Action L = new DynamicAction("L");
    public static Action La = new DynamicAction("L'");
    public static Action L2 = new DynamicAction("L2");
    public static Action R = new DynamicAction("R");
    public static Action Ra = new DynamicAction("R'");
    public static Action R2 = new DynamicAction("R2");
    public static Action U = new DynamicAction("U");
    public static Action Ua = new DynamicAction("U'");
    public static Action U2 = new DynamicAction("U2");
    public static Action D = new DynamicAction("D");
    public static Action Da = new DynamicAction("D'");
    public static Action D2 = new DynamicAction("D2");
    public static Action F = new DynamicAction("F");
    public static Action Fa = new DynamicAction("F'");
    public static Action F2 = new DynamicAction("F2");
    public static Action B = new DynamicAction("B");
    public static Action Ba = new DynamicAction("B'");
    public static Action B2 = new DynamicAction("B2");

    public Cube(int n, boolean ig) {
        this.n = n;
        vect = new char[n];
        u = new Face(n, 'r');
        d = new Face(n, 'o');
        l = new Face(n, 'b');
        r = new Face(n, 'g');
        f = new Face(n, 'w');
        b = new Face(n, 'y');
        if (ig) cg = new CubeGraphics(this);
    }
    
    public Cube (Cube cube) {
        n = cube.getDimension();
        vect = new char[n];
        lastmove = cube.lastmove;
        u = new Face(n, cube.getFace('u'));
        d = new Face(n, cube.getFace('d'));
        l = new Face(n, cube.getFace('l'));
        r = new Face(n, cube.getFace('r'));
        f = new Face(n, cube.getFace('f'));
        b = new Face(n, cube.getFace('b'));
    }
    
    public int getDimension() {
        return n;
    }
    
    public void clearLastMove() {
        lastmove = "";
    }

    public char[][] getFace(char a) {
        switch (a) {
            case 'u':
                return u.state;
            case 'd':
                return d.state;
            case 'l':
                return l.state;
            case 'r':
                return r.state;
            case 'f':
                return f.state;
            case 'b':
                return b.state;
            default:
                return null;
        }
    }
    
    public CubeGraphics getCubeGraphics() {
        return cg;
    }

    public void U() {
        System.arraycopy(f.state[0], 0, vect, 0, n);
        System.arraycopy(r.state[0], 0, f.state[0], 0, n);
        System.arraycopy(b.state[0], 0, r.state[0], 0, n);
        System.arraycopy(l.state[0], 0, b.state[0], 0, n);
        System.arraycopy(vect, 0, l.state[0], 0, n);
        u.turnClock();
        lastmove = "U";
        cg.forceUpdate(this,false);
    }
    
    public void Ua() {
        System.arraycopy(l.state[0], 0, vect , 0, n);
        System.arraycopy(b.state[0], 0, l.state[0], 0, n);
        System.arraycopy(r.state[0], 0, b.state[0], 0, n);
        System.arraycopy(f.state[0], 0, r.state[0], 0, n);
        System.arraycopy(vect, 0, f.state[0], 0, n);
        u.turnCClock();
        lastmove = "U'";
        cg.forceUpdate(this,false);
    }

    public void U2() {
        System.arraycopy(f.state[0], 0, vect, 0, n);
        System.arraycopy(b.state[0], 0, f.state[0], 0, n);
        System.arraycopy(vect, 0, b.state[0], 0, n);
        System.arraycopy(l.state[0], 0, vect, 0, n);
        System.arraycopy(r.state[0], 0, l.state[0], 0, n);
        System.arraycopy(vect, 0, r.state[0], 0, n);
        u.turn180();
        lastmove = "U2";
        cg.forceUpdate(this,false);
    }

    public void D() {
        System.arraycopy(f.state[n - 1], 0, vect, 0, n);
        System.arraycopy(l.state[n - 1], 0, f.state[n - 1], 0, n);
        System.arraycopy(b.state[n - 1], 0, l.state[n - 1], 0, n);
        System.arraycopy(r.state[n - 1], 0, b.state[n - 1], 0, n);
        System.arraycopy(vect, 0, r.state[n - 1], 0, n);
        d.turnClock();
        lastmove = "D";
        cg.forceUpdate(this,false);
    }
    
    public void Da() {
        System.arraycopy(r.state[n - 1], 0, vect, 0, n);
        System.arraycopy(b.state[n - 1], 0, r.state[n - 1], 0, n);
        System.arraycopy(l.state[n - 1], 0, b.state[n - 1], 0, n);        
        System.arraycopy(f.state[n - 1], 0, l.state[n - 1], 0, n);
        System.arraycopy(vect, 0, f.state[n - 1], 0, n);
        d.turnCClock();
        lastmove = "D'";
        cg.forceUpdate(this,false);
    }

    public void D2() {
        System.arraycopy(f.state[n - 1], 0, vect, 0, n);
        System.arraycopy(b.state[n - 1], 0, f.state[n - 1], 0, n);
        System.arraycopy(vect, 0, b.state[n - 1], 0, n);
        System.arraycopy(l.state[n - 1], 0, vect, 0, n);
        System.arraycopy(r.state[n - 1], 0, l.state[n - 1], 0, n);
        System.arraycopy(vect, 0, r.state[n - 1], 0, n);
        u.turn180();
        lastmove = "D2";
        cg.forceUpdate(this,false);
    }

    public void F() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[n - 1][i];
            u.state[n - 1][i] = l.state[n - i - 1][n - 1];
            l.state[n - i - 1][n - 1] = d.state[0][n - i - 1];
            d.state[0][n - i - 1] = r.state[i][0];
            r.state[i][0] = vect[i];
        }
        f.turnClock();
        lastmove = "F";
        cg.forceUpdate(this,false);
    }
    
    public void Fa() {
        for (int i = 0; i < n; i++) {
            vect[i] = r.state[i][0];
            r.state[i][0] = d.state[0][n - i - 1];
            d.state[0][n - i - 1] = l.state[n - i - 1][n - 1];
            l.state[n - i - 1][n - 1] = u.state[n - 1][i];
            u.state[n - 1][i] = vect[i];
        }
        f.turnCClock();
        lastmove = "F'";
        cg.forceUpdate(this,false);
    }

    public void F2() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[n - 1][i];
            u.state[n - 1][i] = d.state[0][n - i - 1];
            d.state[0][n - i - 1] = vect[i];
            vect[i] = l.state[i][n - 1];
            l.state[i][n - 1] = r.state[n - i - 1][0];
            r.state[n - i - 1][0] = vect[i];
        }
        f.turn180();
        lastmove = "F2";
        cg.forceUpdate(this,false);
    }

    public void B() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[0][i];
            u.state[0][i] = r.state[i][n - 1];
            r.state[i][n - 1] = d.state[n - 1][n - i - 1];
            d.state[n - 1][n - i - 1] = l.state[n - i - 1][0];
            l.state[n - i - 1][0] = vect[i];
        }
        b.turnClock();
        lastmove = "B";
        cg.forceUpdate(this,false);
    }
    
    public void Ba() {
        for (int i = 0; i < n; i++) {
            vect[i] = l.state[n - i - 1][0];
            l.state[n - i - 1][0] = d.state[n - 1][n - i - 1];
            d.state[n - 1][n - i - 1] = r.state[i][n - 1];
            r.state[i][n - 1] = u.state[0][i];
            u.state[0][i] = vect[i];
        }
        b.turnCClock();
        lastmove = "B'";
        cg.forceUpdate(this,false);
    }    

    public void B2() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[0][i];
            u.state[0][i] = d.state[n - 1][n - i - 1];
            d.state[n - 1][n - i - 1] = vect[i];
            vect[i] = r.state[i][n - 1];
            r.state[i][n - 1] = l.state[n - i - 1][0];
            l.state[n - i - 1][0] = vect[i];
        }
        b.turn180();
        lastmove = "B2";
        cg.forceUpdate(this,false);
    }

    public void L() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[i][0];
            u.state[i][0] = b.state[n - i - 1][n - 1];
            b.state[n - i - 1][n - 1] = d.state[i][0];
            d.state[i][0] = f.state[i][0];
            f.state[i][0] = vect[i];
        }
        l.turnClock();
        lastmove = "L";
        cg.forceUpdate(this,false);
    }
    
    public void La() {
        for (int i = 0; i < n; i++) {
            vect[i] = f.state[i][0];
            f.state[i][0] = d.state[i][0];
            d.state[i][0] = b.state[n - i - 1][n - 1];
            b.state[n - i - 1][n - 1] = u.state[i][0];
            u.state[i][0] = vect[i];
        }
        l.turnCClock();
        lastmove = "L'";
        cg.forceUpdate(this,false);
    }
    
    public void L2() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[i][0];
            u.state[i][0] = d.state[i][0];
            d.state[i][0] = vect[i];
            vect[i] = f.state[i][0];
            f.state[i][0] = b.state[n - i - 1][n - 1];
            b.state[n - i - 1][n - 1] = vect[i];
        }
        l.turn180();
        lastmove = "L2";
        cg.forceUpdate(this,false);
    }

    public void R() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[i][n - 1];
            u.state[i][n - 1] = f.state[i][n - 1];
            f.state[i][n - 1] = d.state[i][n - 1];
            d.state[i][n - 1] = b.state[n - i - 1][0];
            b.state[n - i - 1][0] = vect[i];
        }
        r.turnClock();
        lastmove = "R";
        cg.forceUpdate(this,false);
    }
    
    public void Ra() {
        for (int i = 0; i < n; i++) {
            vect[i] = b.state[n - i - 1][0];
            b.state[n - i - 1][0] = d.state[i][n - 1];
            d.state[i][n - 1] = f.state[i][n - 1];
            f.state[i][n - 1] = u.state[i][n - 1];
            u.state[i][n - 1] = vect[i];
        }
        r.turnCClock();
        lastmove = "R'";
        cg.forceUpdate(this,false);
    }

    public void R2() {
        for (int i = 0; i < n; i++) {
            vect[i] = u.state[i][n - 1];
            u.state[i][n - 1] = d.state[i][n - 1];
            d.state[i][n - 1] = vect[i];
            vect[i] = f.state[i][n - 1];
            f.state[i][n - 1] = b.state[n - i - 1][0];
            b.state[n - i - 1][0] = vect[i];
        }
        r.turn180();
        lastmove = "R2";
        cg.forceUpdate(this,false);
    }
    
    public boolean canMove(Action move) {
                if (lastmove.isEmpty()) return true;
		if (move.equals(L) || move.equals(La) || move.equals(L2)){                    
                    if (lastmove.charAt(0) == 'L') {                        
			return false;
                    }
                }
                else if (move.equals(R) || move.equals(Ra) || move.equals(R2)){
                    if (lastmove.charAt(0) == 'R') {
			return false;
                    }
                }
                else if (move.equals(U) || move.equals(Ua) || move.equals(U2)){
                    if (lastmove.charAt(0) == 'U')
			return false;
                }
                else if (move.equals(D) || move.equals(Da) || move.equals(D2)){
                    if (lastmove.charAt(0) == 'D')
			return false;
                }
                else if (move.equals(F) || move.equals(Fa) || move.equals(F2)){
                    if (lastmove.charAt(0) == 'F')
			return false;
                }
                else if (move.equals(B) || move.equals(Ba) || move.equals(B2)){
                    if (lastmove.charAt(0) == 'B')
			return false;
                }
                return true;
	}
    
    public boolean compareCubes(Cube c) {
        boolean retVal = true;
        if(!(this.u.equals(c.u) && this.d.equals(c.d) &&  this.l.equals(c.l) &&
           this.r.equals(c.r) && this.f.equals(c.f) &&  this.b.equals(c.b))) retVal = false;
        return retVal;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if ((o == null) || !(o instanceof Cube)) {
            return false;
        }
        return compareCubes((Cube) o);
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 79 * hash + (this.u != null ? this.u.hashCode() : 0);
        hash = 79 * hash + (this.d != null ? this.d.hashCode() : 0);
        hash = 79 * hash + (this.l != null ? this.l.hashCode() : 0);
        hash = 79 * hash + (this.r != null ? this.r.hashCode() : 0);
        hash = 79 * hash + (this.f != null ? this.f.hashCode() : 0);
        hash = 79 * hash + (this.b != null ? this.b.hashCode() : 0);
        return hash;
    }

    public void print() {
        System.out.println(getCubePic());
    }

    public String getCubePic() {
        StringBuilder buffer = new StringBuilder();
        char[] lfrb = {'l', 'f', 'r', 'b'};
        for (int i = 0; i < n; i++) {
            for (int k = 0; k < n * 2; k++) {
                buffer.append(" ");
            }
            for (int j = 0; j < n; j++) {
                buffer.append(getFace('u')[i][j]);
                buffer.append(" ");
            }
            buffer.append("\n");
        }

        for (int k = 0; k < n; k++) {
            for (int i = 0; i < lfrb.length; i++) {
                for (int j = 0; j < n; j++) {
                    buffer.append(getFace(lfrb[i])[k][j]);
                    buffer.append(" ");
                }
            }
            buffer.append("\n");
        }
        for (int i = 0; i < n; i++) {
            for (int k = 0; k < n * 2; k++) {
                buffer.append(" ");
            }
            for (int j = 0; j < n; j++) {
                buffer.append(getFace('d')[i][j]);
                buffer.append(" ");
            }
            buffer.append("\n");
        }
        return buffer.toString();
    }

    @Override
    public String toString() {
        StringBuilder buffer = new StringBuilder();
        char[] lfrb = {'l', 'f', 'r', 'b'};
        for (int i = 0; i < n; i++) {
            for (int k = 0; k < n * 2; k++) {
                buffer.append(" ");
            }
            for (int j = 0; j < n; j++) {
                buffer.append(getFace('u')[i][j]);
                buffer.append(" ");
            }
            buffer.append("\n");
        }

        for (int k = 0; k < n; k++) {
            for (int i = 0; i < lfrb.length; i++) {
                for (int j = 0; j < n; j++) {
                    buffer.append(getFace(lfrb[i])[k][j]);
                    buffer.append(" ");
                }
            }
            buffer.append("\n");
        }
        for (int i = 0; i < n; i++) {
            for (int k = 0; k < n * 2; k++) {
                buffer.append(" ");
            }
            for (int j = 0; j < n; j++) {
                buffer.append(getFace('d')[i][j]);
                buffer.append(" ");
            }
            buffer.append("\n");
        }
        return buffer.toString();
    }

    private class Face {

        char[][] state;

        private Face(int n, char c) {
            state = new char[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    state[i][j] = c;
                }
            }
        }

        private Face(int n, char[][] face) {
            state = new char[n][n];
            for (int i = 0; i < n; i++) {
                System.arraycopy(face[i], 0, state[i], 0, n);
            }
        }

        private void turnClock() {
            char[][] turned = new char[n][n];
            for (int i = n - 1; i >= 0; i--) {
                for (int j = 0; j < n; j++) {
                    turned[j][n - i - 1] = state[i][j];
                }
            }
            state = turned;
        }

        private void turn180() {
            char[][] turned = new char[n][n];
            for (int i = n - 1; i >= 0; i--) {
                for (int j = 0; j < n; j++) {
                    turned[n - i - 1][n - j - 1] = state[i][j];
                }
            }
            state = turned;
        }

        private void turnCClock() {
            char[][] turned = new char[n][n];
            for (int i = n - 1; i >= 0; i--) {
                for (int j = 0; j < n; j++) {
                    turned[n - j - 1][i] = state[i][j];
                }
            }
            state = turned;
        }
        
        @Override
        public boolean equals(Object o) {
            if(!(o instanceof Face)) return false;
            Face face = (Face) o;
            for(int i = 0; i<n; i++) {
                if(!Arrays.equals(this.state[i], face.state[i]))
                    return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 97 * hash + Arrays.deepHashCode(this.state);
            return hash;
        }
    }
}